Skip to main content

序列化与反序列化

引言

反序列漏洞在安全领域可谓红极一时, 不同语言都拥有此类方法, 且多少都拥有相关的漏洞

为什么反序列化常常带来安全隐患?

首先一门成熟的语言, 如果需要在网络上传递信息, 通常会用到一些格式化数据, 比如 : JSON、XML, 这满足了不同情况下前端和后端的通信需求, 但是这两个数据格式都有一个通用的问题就是不支持复杂的数据类型

基于上面的问题, 也产生了一些特殊的工具, 比如 jackson 和 Fastjson 这类序列化库, 在 JSON(XML) 的基础上进行改造, 通过特定的语法来传递对象, 又或者 RMI 直接使用 Java 等语言内置的序列化方法, 将一个对象转换成一串二进制数据进行传输.

不管是 Jackson 、Fastjson 还是编程语言内置的序列化方法, 一旦涉及到序列化与反序列化数据, 就可能会涉及到安全问题, 但是首先需要明确的是, "反序列化漏洞" 是指对一类漏洞的泛指, 而不是专指某种反序列化方法导致的漏洞

反序列化方法的对比

tip

我主要研究 Java 和 Python, 因此对于 PHP 只是理解即可

首先, Java 的反序列化和 PHP 的反序列化其实有点类似, 他们都是只能将一个对象中的属性按照某种特定的格式生成一段数据流, 在反序列化的时候再按照这个格式将属性赋值给新的对象.

但 Java 相对 PHP 序列化更深入的地方在于, 其提供了更加高级、灵活的方法 writeObject() , 这允许开发者在序列化流中插入一些自定义数据 , 进而在反序列化的时候可以使用 readObject() 读取使用.

当然 PHP 也提供了一个魔术方法叫 __wakeup() , 会在反序列化的时候进行触发.

Question IconJava的 readObject() 和PHP的 __weakeup() 是否类似?

首先两者触发时间一致, 都是在反序列化的时候触发, 但是二者处理数据的方式有差异, Java 的 readObject() 的倾向于解决反序列化时如何还原一个完整对象, 而 PHP 的 __weakup() 更倾向于解决反序列化后如何初始化这个对象

PHP 反序列化

首先来看一段标准的 PHP 反序列化后的数据

<?php 
class Ctf{
public $flag;
public $name='cxk';
public $age='10';
}
class Flag{
public $flag;
}
$ctfer=new Ctf();
$flag=new Flag();
//实例化一个对象
$ctfer->flag=$flag;
$ctfer->name='Sch0lar';
$ctfer->age='18';
echo serialize($ctfer);
?>
tip

从上面可以看到, 在 PHP 序列化后的数据中, 只是描述对象的值是什么

我们知道 PHP 的序列化操作开发者是无法参与的, 通过调用 serialize() 就会产生序列化数据, 我们就可以得到一个完整的对象, 并不能在序列化数据流中新增某一个内容, 如果我们想插入一些内容, 只能将其保存在对象的属性中, 也就是说 PHP 的序列化、反序列操作都是一个纯内部操作, 而 __sleep() __weakup() 这类方法的目的是在序列化、反序列化操作前后执行一些操作

经典的 PHP 序列化操作例子, 就是含有资源类型的 PHP 类, 如: 数据库连接:

<?php
class Connection{
protected $link;
private $dsn, $username, $password;

public function __construct($dsn, $username, $password){
$this->dsn = $dsn;
$this->username = $username;
$this->password = $password;
$this->connect();
}

private function connect(){
$this->link = new PDO($this->dsn, $this->username, $this->password);
}
}
?>

在 PHP 中, 资源类型的对象默认是不会写入序列化数据中的, 那么上述 Connection 类的 $link 在序列化后就是 null , 反序列化时拿到的也是 null. 那么, 如果想要实现在反序列化后 $link 是一个数据库连接, 就需要编写 __weakup()

<?php
class Connection{
protected $link;
private $dsn, $username, $password;

public function __construct($dsn, $username, $password){
$this->dsn = $dsn;
$this->username = $username;
$this->password = $password;
$this->connect();
}

private function connect(){
$this->link = new PDO($this->dsn, $this->username, $this->password);
}

public function __sleep(){
return array('dsn', 'username', 'password');
}

public function __wakeup(){
$this->connect();
}
?>

这里的 __weakup() 的作用是在反序列化之后拿到 Connection 对象, 执行 connect() 函数连接数据库

__weakup() 作用是在反序列化之后执行一些操作, 但其实我们很少利用序列化数据传递资源类型的对象, 而其他类型的随心, 在反序列化的时候就已经赋值了.

所以我们可以发现, PHP 的反序列化漏洞, 很少是由 __weakup() 触发的, 通常触发在析构函数 __destruct() , 其实大部分PHP反序列化漏洞,都并不是由反序列化导致的,只是通过反序列化可以 控制对象的属性,进而在后续的代码中进行危险操作。

Java 反序列化

tip

Java 反序列化的操作, 很多是需要开发者进行参与的, 所以我们可以发现大量的库会实现 readObject()writeObject()

ava 在序列化一个对象时, 会调用对象中的 writeObject(ObjectOutputStream) 开发者可以将任何内容写入这个 stream 中, 在反序列化时, 会通过调用 readObject() 开发者也可以借此读取处前面写入的数据进行处理

package com.jtz;

import java.io.*;

public class Person implements Serializable {
public String name;
public int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject("This is a object");
}

private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
String message = (String) s.readObject();
System.out.println(message);
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
Person jtz = new Person("JTZ", 18);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(jtz);

ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
}

Python 反序列化

Python 的反序列化和 Java 、PHP有个明显的区别, 就是 Python 的反序列过程实际上是在执行一个基于栈的虚拟机, 我们可以向栈上增、删对象, 也可以执行一些指令, 比如函数的执行, 甚至可以利用这个虚拟机执行一个完整的应用程序, 所以 Python 的反序列化可以立即导致任意函数、命令执行漏洞, 相比于 PHP 和 Java 更加危险.

参考

  • Java 安全漫谈-07反序列化篇(1)